/**
 * The <code>Naming</code> class provides methods for storing and obtaining
 * references to remote objects in a remote object registry.  Each method of
 * the <code>Naming</code> class takes as one of its arguments a name that
 * is a <code>java.lang.String</code> in URL format (without the
 * scheme component) of the form:
 *
 * <PRE>
 *    //host:port/name
 * </PRE>
 * 
 * <P>where <code>host</code> is the host (remote or local) where the registry
 * is located, <code>port</code> is the port number on which the registry
 * accepts calls, and where <code>name</code> is a simple string uninterpreted
 * by the registry. Both <code>host</code> and <code>port</code> are optional.
 * If <code>host</code> is omitted, the host defaults to the local host. If
 * <code>port</code> is omitted, then the port defaults to 1099, the
 * "well-known" port that RMI's registry, <code>rmiregistry</code>, uses.
 *
 * <P><em>Binding</em> a name for a remote object is associating or
 * registering a name for a remote object that can be used at a later time to
 * look up that remote object.  A remote object can be associated with a name
 * using the <code>Naming</code> class's <code>bind</code> or
 * <code>rebind</code> methods.
 *
 * <P>Once a remote object is registered (bound) with the RMI registry on the
 * local host, callers on a remote (or local) host can lookup the remote
 * object by name, obtain its reference, and then invoke remote methods on the
 * object.  A registry may be shared by all servers running on a host or an
 * individual server process may create and use its own registry if desired
 * (see <code>java.rmi.registry.LocateRegistry.createRegistry</code> method
 * for details).
 *
 * @version 1.0, 09/05/99
 * @author  Ann Wollrath
 * @author  Roger Riggs
 * @author  Cody Stoutenburg
 * @since   JDK1.1
 * @see     java.rmi.registry.Registry
 * @see     java.rmi.registry.LocateRegistry
 * @see     java.rmi.registry.LocateRegistry#createRegistry(int)
 * @see 	java.rmi.Naming
 */
package registry;

import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.rmi.AccessException;
import java.rmi.AlreadyBoundException;
import java.rmi.Naming;
import java.rmi.NotBoundException;
import java.rmi.Remote;
import java.rmi.RemoteException;
import java.rmi.registry.Registry;

/**
 * @author Cody Stoutenburg
 * 
 */
public class RmiRegistry {

	private static IRmiRegistryProxy getProxy(String url)
			throws MalformedURLException, RemoteException, NotBoundException {
		ParsedNamingURL parsed = parseURL(url);

		return (IRmiRegistryProxy) Naming.lookup("//" + parsed.host + ":"
				+ parsed.port + "/" + IRmiRegistryProxy.RMI_NAME);
	}

	/**
	 * Returns a reference, a stub, for the remote object associated with the
	 * specified <code>name</code>.
	 * 
	 * @param name
	 *            a name in URL format (without the scheme component)
	 * @return a reference for a remote object
	 * @exception NotBoundException
	 *                if name is not currently bound
	 * @exception RemoteException
	 *                if registry could not be contacted
	 * @exception MalformedURLException
	 *                if the name is not an appropriately formatted URL
	 */
	public static Remote lookup(String name) throws NotBoundException,
			java.net.MalformedURLException, RemoteException {
		ParsedNamingURL parsed = parseURL(name);

		return getProxy(name).lookup(parsed.name);
	}
	/**
	 * Binds the specified <code>name</code> to a remote object.
	 * 
	 * @param name
	 *            a name in URL format (without the scheme component)
	 * @param obj
	 *            a reference for the remote object (usually a stub)
	 * @exception AlreadyBoundException
	 *                if name is already bound
	 * @exception MalformedURLException
	 *                if the name is not an appropriately formatted URL
	 * @exception RemoteException
	 *                if registry could not be contacted
	 * @throws NotBoundException
	 */
	public static void bind(String name, Remote obj)
			throws AlreadyBoundException, java.net.MalformedURLException,
			RemoteException, NotBoundException {
		getProxy(name).bind(name, obj);
	}

	/**
	 * Destroys the binding for the specified name that is associated with a
	 * remote object.
	 * 
	 * @param name
	 *            a name in URL format (without the scheme component)
	 * @exception NotBoundException
	 *                if name is not currently bound
	 * @exception MalformedURLException
	 *                if the name is not an appropriately formatted URL
	 * @exception RemoteException
	 *                if registry could not be contacted
	 * @exception AccessException
	 *                if this operation is not permitted (if originating from a
	 *                non-local host, for example)
	 * @since JDK1.1
	 */
	public static void unbind(String name) throws RemoteException,
			NotBoundException, java.net.MalformedURLException {
		getProxy(name).unbind(name);
	}

	/**
	 * Rebinds the specified name to a new remote object. Any existing binding
	 * for the name is replaced.
	 * 
	 * @param name
	 *            a name in URL format (without the scheme component)
	 * @param obj
	 *            new remote object to associate with the name
	 * @exception MalformedURLException
	 *                if the name is not an appropriately formatted URL
	 * @exception RemoteException
	 *                if registry could not be contacted
	 * @throws NotBoundException
	 */
	public static void rebind(String name, Remote obj) throws RemoteException,
			java.net.MalformedURLException, NotBoundException {
		getProxy(name).rebind(name, obj);
	}

	/**
	 * Returns an array of the names bound in the registry. The names are
	 * URL-formatted (without the scheme component) strings. The array contains
	 * a snapshot of the names present in the registry at the time of the call.
	 * 
	 * @param name
	 *            a registry name in URL format (without the scheme component)
	 * @return an array of names (in the appropriate format) bound in the
	 *         registry
	 * @exception MalformedURLException
	 *                if the name is not an appropriately formatted URL
	 * @throws NotBoundException
	 */
	public static String[] list(String name) throws RemoteException,
			java.net.MalformedURLException, NotBoundException {
		return getProxy(name).list(name);
	}

	/**
	 * Dissect Naming URL strings to obtain referenced host, port and object
	 * name.
	 * 
	 * @return an object which contains each of the above components.
	 * 
	 * @exception MalformedURLException
	 *                if given url string is malformed
	 */
	private static ParsedNamingURL parseURL(String str)
			throws MalformedURLException {
		try {
			return intParseURL(str);
		} catch (URISyntaxException ex) {
			/*
			 * With RFC 3986 URI handling, 'rmi://:<port>' and '//:<port>' forms
			 * will result in a URI syntax exception Convert the authority to a
			 * localhost:<port> form
			 */
			MalformedURLException mue = new MalformedURLException(
					"invalid URL String: " + str);
			mue.initCause(ex);
			int indexSchemeEnd = str.indexOf(':');
			int indexAuthorityBegin = str.indexOf("//:");
			if (indexAuthorityBegin < 0) {
				throw mue;
			}
			if ((indexAuthorityBegin == 0)
					|| ((indexSchemeEnd > 0) && (indexAuthorityBegin == indexSchemeEnd + 1))) {
				int indexHostBegin = indexAuthorityBegin + 2;
				String newStr = str.substring(0, indexHostBegin) + "localhost"
						+ str.substring(indexHostBegin);
				try {
					return intParseURL(newStr);
				} catch (URISyntaxException inte) {
					throw mue;
				} catch (MalformedURLException inte) {
					throw inte;
				}
			}
			throw mue;
		}
	}

	private static ParsedNamingURL intParseURL(String str)
			throws MalformedURLException, URISyntaxException {
		URI uri = new URI(str);
		if (uri.isOpaque()) {
			throw new MalformedURLException("not a hierarchical URL: " + str);
		}
		if (uri.getFragment() != null) {
			throw new MalformedURLException(
					"invalid character, '#', in URL name: " + str);
		} else if (uri.getQuery() != null) {
			throw new MalformedURLException(
					"invalid character, '?', in URL name: " + str);
		} else if (uri.getUserInfo() != null) {
			throw new MalformedURLException(
					"invalid character, '@', in URL host: " + str);
		}
		String scheme = uri.getScheme();
		if (scheme != null && !scheme.equals("rmi")) {
			throw new MalformedURLException("invalid URL scheme: " + str);
		}

		String name = uri.getPath();
		if (name != null) {
			if (name.startsWith("/")) {
				name = name.substring(1);
			}
			if (name.length() == 0) {
				name = null;
			}
		}

		String host = uri.getHost();
		if (host == null) {
			host = "";
			try {
				/*
				 * With 2396 URI handling, forms such as 'rmi://host:bar' or
				 * 'rmi://:<port>' are parsed into a registry based authority.
				 * We only want to allow server based naming authorities.
				 */
				uri.parseServerAuthority();
			} catch (URISyntaxException use) {
				// Check if the authority is of form ':<port>'
				String authority = uri.getAuthority();
				if (authority != null && authority.startsWith(":")) {
					// Convert the authority to 'localhost:<port>' form
					authority = "localhost" + authority;
					try {
						uri = new URI(null, authority, null, null, null);
						// Make sure it now parses to a valid server based
						// naming authority
						uri.parseServerAuthority();
					} catch (URISyntaxException use2) {
						throw new MalformedURLException("invalid authority: "
								+ str);
					}
				} else {
					throw new MalformedURLException("invalid authority: " + str);
				}
			}
		}
		int port = uri.getPort();
		if (port == -1) {
			port = Registry.REGISTRY_PORT;
		}
		return new ParsedNamingURL(host, port, name);
	}

	/**
	 * Simple class to enable multiple URL return values.
	 */
	private static class ParsedNamingURL {
		String host;
		int port;
		String name;

		ParsedNamingURL(String host, int port, String name) {
			this.host = host;
			this.port = port;
			this.name = name;
		}
	}

}
